package rpc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

import appserver.Constants;

import session.Session;
import session.SessionID;

public class RPCServer implements Runnable {
	
	private int port;
	private ConcurrentHashMap<SessionID,Session> SSTable;
	private Set<String> s_mbrSet; 
	private static final boolean DEBUG = true;

	public RPCServer(Integer port, ConcurrentHashMap<SessionID,Session> SSTable, Set<String> s_mbrSet) {
		this.port = port;
		this.SSTable = SSTable;
		this.s_mbrSet= s_mbrSet;
	}

	@Override
	public void run() {
        DatagramSocket serverSocket;
		try {
			serverSocket = new DatagramSocket(port);
		} catch (SocketException e1) {
			System.out.println("Could not start socket on port: " + port);
			return;
		}
		//this call unmarshalls an RPCPacket object and acts according to its instruction
		while (true) {
			try {
	        byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            serverSocket.receive(receivePacket);
            System.out.println("Server - received request from: " + receivePacket.getAddress() + Constants.PORT_NUMBER_SEPARATOR + receivePacket.getPort()); 
            ObjectInputStream iStream = null;
            RPCPacket info = null;
            try {
            iStream = new ObjectInputStream(new ByteArrayInputStream(receivePacket.getData()));
            info = (RPCPacket) iStream.readObject();
            }
            catch (StreamCorruptedException e) {
            	System.out.println("Invalid request (not an RPCPacket)");
            	continue;
            } catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
            iStream.close();
            if (info.getCallId() == null) {
            	if (DEBUG) System.out.println("RPCServer received call without id");
            	continue;
            }
            RPCPacket responsePacket = new RPCPacket(info.getCallId());
            switch (info.getOpCode()) {
	            case OpCodes.SESSION_READ: {
	            	String idName = info.getSessId();
	            	SessionID id = new SessionID(idName);
	            	Session sessData = SSTable.get(id);
	            	if (sessData != null) {
	            		synchronized (sessData) {
		            		if (sessData.needsUpdate())
			            	{
			            		sessData.setDiscard_time(Constants.TIMEOUT_UPDATE_SECS);
			            	}
	            		}
	            		if (!SSTable.containsKey(sessData.getSessionID())) {
	            			sessData = null;
	            		}
	            	}
	            	responsePacket.setSessionData(sessData);
	            	break;
	            }
	            case OpCodes.SESSION_WRITE: {
	            	Session sessData = info.getSessionData();
	            	SSTable.put(sessData.getSessionID(), sessData);
	            	break;
	            }
	            case OpCodes.SESSION_DELETE: {
	            	String idName = info.getSessId();
	            	SessionID id = new SessionID(idName);
	            	Session sessData = SSTable.get(id);
	            	SSTable.remove(id);
	            	break;
	            }
            }
            
            responsePacket.setAck(true);  
            if (DEBUG) System.out.println("Server: Responding to call: " + info.getCallId());
            InetAddress IPAddress = receivePacket.getAddress();
            int port = receivePacket.getPort();
            String ipLiteral = IPAddress.toString().split("/")[1];
            s_mbrSet.add(ipLiteral + Constants.PORT_NUMBER_SEPARATOR + info.getRpcPort());
            ByteArrayOutputStream bStream = new ByteArrayOutputStream();
            ObjectOutput oo = new ObjectOutputStream(bStream); 
            oo.writeObject(responsePacket);
            oo.close();
            byte[] sendData = bStream.toByteArray();
            System.out.println("Server - response size is: " + sendData.length);
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
            serverSocket.send(sendPacket);
            System.out.println("Server - sent response to: " + IPAddress + Constants.PORT_NUMBER_SEPARATOR + port);
			}
			catch (IOException e) {
				continue;
			}
		}

	}
	

}
